package pacman.entries.pacman;

import java.util.EnumMap;

import pacman.game.Constants;
import pacman.game.Constants.DM;
import pacman.game.Constants.GHOST;
import pacman.game.Constants.MOVE;
import pacman.game.Game;

/**
 * Represents a gamestate.
 */
public class MyGame
{
	//also see documentation in Game
	private Game game;
	private int[] identifyingPositions;

	public MyGame(Game game)
	{
		this(game, null);
	}

	public MyGame(Game game, int[] identifyingPositions)
	{
		this.game = game;
		this.identifyingPositions = identifyingPositions;
	}

	/**
	 * Gets the possible moves Pacman can take from this gamestate.
	 * 
	 * @return
	 */
	public MOVE[] getPacmanMoves()
	{
		int location = game.getPacmanCurrentNodeIndex();
		MOVE[] directions = game.getPossibleMoves(game.getPacmanCurrentNodeIndex());
		if (directions.length >= 3 //junction
				|| directions[0].opposite() != directions[1] //corner
				|| game.wasPowerPillEaten() || game.getNumGhostsEaten() > 0)
			return game.getPossibleMoves(location);
		return game.getPossibleMoves(location, game.getPacmanLastMoveMade());
	}

	/**
	 * Gets the possible moves the ghosts can take from this gamestate.
	 * 
	 * @return
	 */
	public EnumMap<GHOST, MOVE>[] getGhostMoves()
	{
		MOVE[] bMoves = getGhostMoves(GHOST.BLINKY), pMoves = getGhostMoves(GHOST.PINKY), iMoves = getGhostMoves(GHOST.INKY), sMoves = getGhostMoves(GHOST.SUE);
		EnumMap<GHOST, MOVE>[] moves = new EnumMap[bMoves.length * pMoves.length * iMoves.length * sMoves.length];
		int counter = 0;
		for (MOVE blinky : bMoves)
			for (MOVE pinky : pMoves)
				for (MOVE inky : iMoves)
					for (MOVE sue : sMoves)
					{
						EnumMap<GHOST, MOVE> move = new EnumMap<GHOST, MOVE>(GHOST.class);
						move.put(GHOST.BLINKY, blinky);
						move.put(GHOST.PINKY, pinky);
						move.put(GHOST.INKY, inky);
						move.put(GHOST.SUE, sue);
						moves[counter++] = move;
					}
		return moves;
	}

	private MOVE[] getGhostMoves(GHOST ghost)
	{
		if (game.doesGhostRequireAction(ghost))
			return game.getPossibleMoves(game.getGhostCurrentNodeIndex(ghost), game.getGhostLastMoveMade(ghost));
		return new MOVE[] { game.getGhostLastMoveMade(ghost) };
	}

	/**
	 * Iterates from this gamestate with the given moves for Pacman and the ghosts until the game branches, or is allowed to branch:<br>
	 * 1) A ghost reaches a junction, Pacman reaches a junction, or Pacman reaches a corner (ghosts at corners don't count because they can't reverse)<br>
	 * 2) A ghost or Pacman dies<br>
	 * 3) A power pill is eaten<br>
	 * 4) The level ends
	 * 
	 * @param pacman
	 *            The move Pacman should take.
	 * @param ghost
	 *            The moves the ghosts should take.
	 * @return The gamestate that results from Pacman and the ghosts repeatedly moving in the given directions until the game branches as described above.
	 */
	public MyGame getNeighbor(MOVE pacman, EnumMap<GHOST, MOVE> ghostMove)
	{
		Game game = this.game.copy();
		game.advanceGameWithPowerPillReverseOnly(pacman, ghostMove);

		MOVE[] directions;
		int[] identifyingPositions = new int[] { game.getCurrentLevel(), game.getPacmanCurrentNodeIndex(), game.getGhostCurrentNodeIndex(GHOST.BLINKY), game.getGhostCurrentNodeIndex(GHOST.PINKY), game.getGhostCurrentNodeIndex(GHOST.INKY), game.getGhostCurrentNodeIndex(GHOST.SUE) };

		//if any of these are true, stop - we're at a branching point (numbers refer to the criteria in the documentation of this method)
		while (!(game.doesGhostRequireAction(GHOST.BLINKY) || game.doesGhostRequireAction(GHOST.PINKY) || game.doesGhostRequireAction(GHOST.INKY) || game.doesGhostRequireAction(GHOST.SUE) //ghost is at a junction (1)
				|| (directions = game.getPossibleMoves(game.getPacmanCurrentNodeIndex())).length >= 3 //pacman is at a junction (1)
				|| directions[0].opposite() != directions[1] //pacman is at a corner (1)
				|| game.wasPacManEaten() || game.wasPowerPillEaten() || game.getNumGhostsEaten() > 0 //a ghost or pacman dies (2), or a power pill is eaten (3)
		|| game.getCurrentLevelTime() == 0)) //new level (4)
		{
			game.advanceGameWithPowerPillReverseOnly(pacman, ghostMove);
		}
		return new MyGame(game, identifyingPositions);
	}

	public int closestPillNode()
	{
		double distance = 99999;
		int node = -1;
		int pacmanLocation = game.getPacmanCurrentNodeIndex();
		int[] activePills = game.getActivePillsIndices();
		for (int pill : activePills)
		{
			double thisDistance = game.getDistance(pacmanLocation, pill, DM.PATH);
			if (thisDistance < distance)
			{
				distance = thisDistance;
				node = pill;
			}
		}
		//		GameView.addPoints(game, Color.WHITE, node);
		return node;
	}

	public boolean isAnyGhostsEdibleNow()
	{
		for (GHOST c : GHOST.values())
			if (MyPacMan.gg.isGhostEdible(c))
				return true;
		return false;
	}

	public double evaluate()
	{
		//		GameView.addPoints(game, Color.BLUE, getPacmanLocation());

		double score = game.getScore() / 20d;

		int pacmanLocation = game.getPacmanCurrentNodeIndex();
		for (GHOST ghost : GHOST.values())
		{
			if (game.isGhostEdible(ghost))
			{
				score += (game.getGhostEdibleTime(ghost) * 0.3 - game.getDistance(pacmanLocation, game.getGhostCurrentNodeIndex(ghost), DM.PATH)) / 5d;
			}
		}

		if (MyPacMan.gg.getNumberOfActivePowerPills() - game.getNumberOfActivePowerPills() > 0 && isAnyGhostsEdibleNow())
			score -= 40;

		if (game.wasPacManEaten())
			score += (game.getCurrentLevelTime() - 4500) * 2;

		if (game.getCurrentLevelTime() == 0)
			score += (game.getCurrentLevel() * Constants.LEVEL_LIMIT - game.getTotalTime()) / 20d;
		else
			score -= game.getDistance(pacmanLocation, game.getGhostInitialNodeIndex(), DM.PATH) / 1500d;

		//Fast End Game
		//Deactivate when ghosts are edible to prioritize ghosts
		if (game.getNumberOfActivePills() <= 64 && !isAnyGhostsEdibleNow())
			score -= game.getDistance(pacmanLocation, closestPillNode(), DM.PATH) / 800d;

		return score;
	}

	public boolean isReset()
	{
		return game.wasPacManEaten() || game.getCurrentLevelTime() == 0;
	}

	public int getPacmanLocation()
	{
		return game.getPacmanCurrentNodeIndex();
	}

	public int getGhostLocation(GHOST ghost)
	{
		return game.getGhostCurrentNodeIndex(ghost);
	}

	public int[] getIdentifyingPositions()
	{
		return identifyingPositions;
	}

}
